package com.action.gulimall.search.service.impl;

import com.action.common.constant.SearchConstant;
import com.action.common.model.es.SkuEsModel;
import com.action.common.utils.R;
import com.action.gulimall.search.config.RestClientConfig;
import com.action.gulimall.search.feign.ProductFeignService;
import com.action.gulimall.search.service.MallSearchService;
import com.action.gulimall.search.vo.AttrRespVo;
import com.action.gulimall.search.vo.SearchParam;
import com.action.gulimall.search.vo.SearchResult;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author sl shilun217@qq.com
 * @Deacription 商品搜索逻辑
 * @date 2021/06/23/1:06
 **/
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    RestHighLevelClient restHighLevelClient;
    @Autowired
    ProductFeignService productFeignService;

    @Override
    public SearchResult search(SearchParam searchParam) throws IOException { ;
        //构建查询语句
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        //执行查询
        SearchResponse searchResponse = restHighLevelClient
                .search(searchRequest, RestClientConfig.COMMON_OPTIONS);
        //解析查询结果
        SearchResult searchResult = parseSearchResponse(searchResponse, searchParam);
        return searchResult;
    }

    private SearchRequest buildSearchRequest(SearchParam searchParam){
        log.info("当前线程{},查询条件searchParam->{}",Thread.currentThread().getId(),searchParam.toString());
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(SearchConstant.PRODUCT_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchRequest.source(searchSourceBuilder);
        //构建query和高亮
        buildSearchQuery(searchSourceBuilder,searchParam);
        //构建聚合  品牌聚合 分类聚合  属性聚合
        buildAggregations(searchSourceBuilder);
        //构建排序和分页
        buildSortAndPage(searchParam, searchSourceBuilder);
        log.info("当前线程{},构建的DSL语句->{}",Thread.currentThread().getId(),searchSourceBuilder.toString());;
        return searchRequest;
    }

    private void buildSortAndPage(SearchParam searchParam, SearchSourceBuilder searchSourceBuilder) {
        //排序#排序 综合排序默认为keyword匹配分数排序 skuPrice saleCount  hotScore
        //形式为sort=hotScore_asc/desc
        if(!StringUtils.isEmpty(searchParam.getSort())){
            String sort = searchParam.getSort();
            String[] sortFileds = sort.split("_");
            SortOrder sortOrder="asc".equalsIgnoreCase(sortFileds[1])?SortOrder.ASC:SortOrder.DESC;
            searchSourceBuilder.sort(sortFileds[0],sortOrder);
        }
        //分页
        searchSourceBuilder.from((searchParam.getPageNum()-1)*SearchConstant.PRODUCT_PAGESIZE);
        searchSourceBuilder.size(SearchConstant.PRODUCT_PAGESIZE);
    }

    private void buildAggregations(SearchSourceBuilder searchSourceBuilder) {
        //品牌聚合
        AggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId");
        AggregationBuilder brandNameAgg = AggregationBuilders.terms("brandNameAgg").field("brandName");
        brandAgg.subAggregation(brandNameAgg);
        AggregationBuilder brandImgAgg = AggregationBuilders.terms("brandImgAgg").field("brandImg");
        brandAgg.subAggregation(brandImgAgg);
        searchSourceBuilder.aggregation(brandAgg);
        //分类聚合
        AggregationBuilder catalongAgg = AggregationBuilders.terms("catalogAgg").field("catalogId");
        AggregationBuilder catalongNameAgg = AggregationBuilders.terms("catalongNameAgg").field("catalogName");
        catalongAgg.subAggregation(catalongNameAgg);
        searchSourceBuilder.aggregation(catalongAgg);
        //属性聚合
        NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("attrAgg", "attrs");
        //按属性Id进行聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
        nestedAggregationBuilder.subAggregation(attrIdAgg);
        //在按属性Id进行聚合的前提下，按属性名称进行聚合
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attrNameAgg").field("attrs.attrName");
        //在按属性Id进行聚合的前提下，按属性值进行聚合
        TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue");
        attrIdAgg.subAggregation(attrNameAgg);
        attrIdAgg.subAggregation(attrValueAgg);
        searchSourceBuilder.aggregation(nestedAggregationBuilder);
    }

    private void buildSearchQuery(SearchSourceBuilder searchSourceBuilder,SearchParam searchParam){
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        searchSourceBuilder.query(boolQueryBuilder);
        //关键字全文检索 skuTitle
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
            //高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle").preTags("<b style='color:red'>").postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }
        //过滤器 过滤(是否有库存hasStock，分类catalogId，品牌brandId，价格区间skuPrice)
        if(searchParam.getHasStock()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock()==0?Boolean.FALSE:Boolean.TRUE));
        }
        if(searchParam.getCatalog3Id()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
        }
        if(!CollectionUtils.isEmpty(searchParam.getBrandId())){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }
        if(!StringUtils.isEmpty(searchParam.getSkuPrice())){
            String[] split = searchParam.getSkuPrice().split("_");
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
            if(split.length==2){
                rangeQueryBuilder.gte(Long.valueOf(split[0])).lte(Long.valueOf(split[1]));
            }else{
                if(searchParam.getSkuPrice().startsWith("_")){
                    rangeQueryBuilder.lte(Long.valueOf(split[0]));
                }else{
                    rangeQueryBuilder.gte(Long.valueOf(split[0]));
                }
            }
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        if(!CollectionUtils.isEmpty(searchParam.getAttrs())){
            searchParam.getAttrs().forEach(attr->{
                BoolQueryBuilder boolQueryBuilder1 = new BoolQueryBuilder();
                //attrs=1_5寸:8寸
                String[] s = attr.split("_");
                String attrId=s[0];
                String[] attrValues = s[1].split(":");//这个属性检索用的值
                boolQueryBuilder1.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                boolQueryBuilder1.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                NestedQueryBuilder nestedQueryBuilder = QueryBuilders
                        .nestedQuery("attrs", boolQueryBuilder1, ScoreMode.None);
                boolQueryBuilder.filter(nestedQueryBuilder);
            });
        }
    }

    private SearchResult parseSearchResponse(SearchResponse searchResponse,SearchParam searchParam){
        SearchResult searchResult = new SearchResult();
        if(searchResponse.getHits().getTotalHits().value>0){
            //组装SkuEsModels
            buildProducts(searchResponse, searchParam, searchResult);
            //组装搜索结果中所有的分类集合
            buildCatalogVos(searchResponse, searchResult);
            //组装搜索结果中所有的品牌集合
            buildBrandVos(searchResponse, searchResult);
            //组装搜索结果中所有的属性集合
            buildAttrVos(searchResponse, searchResult);
            //面包屑导航
            buildNavs(searchParam, searchResult);
        }
        return searchResult;
    }

    private void buildNavs(SearchParam searchParam, SearchResult searchResult) {
        if (searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0) {
            List<SearchResult.NavVo> collect = searchParam.getAttrs().stream().map(attr -> {
                //1、分析每一个attrs传过来的参数值
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] s = attr.split("_");
                navVo.setNavValue(s[1]);
                R r = productFeignService.attrInfo(Long.parseLong(s[0]));
                if (r.getCode() == 0) {
                    AttrRespVo data = r.getData(new TypeReference<AttrRespVo>(){}).get();
                    if(data!=null){
                        navVo.setNavName(data.getAttrName());
                    }
                } else {
                    log.info("远程查询产品属性出现异常{}",JSON.toJSONString(r));
                    navVo.setNavName(s[0]);
                }
                //2、取消了这个面包屑以后，我们要跳转到哪个地方，将请求的地址url里面的当前置空
                //拿到所有的查询条件，去掉当前
                String encode = null;
                try {
                    encode = URLEncoder.encode(attr,"UTF-8");
                    encode.replace("+","%20");  //浏览器对空格的编码和Java不一样，差异化处理
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                String replace = searchParam.get_queryString().replace("&attrs=" + attr, "");
                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
                return navVo;
            }).collect(Collectors.toList());
            searchResult.setNavs(collect);
        }
    }

    private void buildAttrVos(SearchResponse searchResponse, SearchResult searchResult) {
        Nested attrAgg = searchResponse.getAggregations().get("attrAgg");
        Terms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        if(!CollectionUtils.isEmpty(attrIdAggBuckets)){
            List<SearchResult.AttrVo> attrVos = attrIdAggBuckets.stream().map(bucket -> {
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                String attrIdStr = ((Terms.Bucket) bucket).getKeyAsString();
                attrVo.setAttrId(Long.valueOf(attrIdStr));
                Terms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
                attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
                Terms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
                List<String> attrValues = attrValueAgg.getBuckets().stream()
                        .map(item ->((Terms.Bucket) item).getKeyAsString())
                        .collect(Collectors.toList());
                attrVo.setAttrValue(attrValues);
                return attrVo;
            }).collect(Collectors.toList());

            searchResult.setAttrs(attrVos);
        }
    }

    private void buildBrandVos(SearchResponse searchResponse, SearchResult searchResult) {
        Terms brandAgg = searchResponse.getAggregations().get("brandAgg");
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
        if(!CollectionUtils.isEmpty(brandAggBuckets)){
            List<SearchResult.BrandVo> brandVos = brandAggBuckets.stream().map(bucket->{
                SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                String brandIdStr = ((Terms.Bucket) bucket).getKeyAsString();
                brandVo.setBrandId(Long.valueOf(brandIdStr));
                Terms brandNameAgg = ((Terms.Bucket) bucket).getAggregations().get("brandNameAgg");
                brandVo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());
                Terms brandImgAgg = ((Terms.Bucket) bucket).getAggregations().get("brandImgAgg");
                brandVo.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString());
                return brandVo;
            }).collect(Collectors.toList());
            searchResult.setBrands(brandVos);
        }
    }

    private void buildCatalogVos(SearchResponse searchResponse, SearchResult searchResult) {
        Terms catalogAgg = searchResponse.getAggregations().get("catalogAgg");
        List<? extends Terms.Bucket> catalongAggBuckets = catalogAgg.getBuckets();
        if(!CollectionUtils.isEmpty(catalongAggBuckets)){
            List<SearchResult.CatalogVo> catalogVos = catalongAggBuckets.stream().map(bucket -> {
                SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
                String catalogId = ((Terms.Bucket) bucket).getKeyAsString();
                catalogVo.setCatalogId(Long.valueOf(catalogId));
                Terms catalongNameAgg = ((Terms.Bucket) bucket).getAggregations().get("catalongNameAgg");
                catalogVo.setCatalogName(catalongNameAgg.getBuckets().get(0).getKeyAsString());
                return catalogVo;
            }).collect(Collectors.toList());
            searchResult.setCatalogs(catalogVos);
        }
    }

    private void buildProducts(SearchResponse searchResponse, SearchParam searchParam, SearchResult searchResult) {
        List<SkuEsModel> skuEsModels = new ArrayList<SkuEsModel>();
        searchResponse.getHits().forEach(hit->{
            SkuEsModel skuEsModel = JSON.parseObject(hit.getSourceAsString(),SkuEsModel.class);
            //添加高亮信息
            if(!StringUtils.isEmpty(searchParam.getKeyword())) {
                HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                skuEsModel.setSkuTitle(skuTitle.toString());
            }
            skuEsModels.add(skuEsModel);
        });
        searchResult.setProduct(skuEsModels);
        //组装分页
        TotalHits totalHits = searchResponse.getHits().getTotalHits();
        int total = Long.valueOf(totalHits.value).intValue();
        //分页信息-总记录数
        searchResult.setTotal(Long.valueOf(total).intValue());
        //分页信息-页码
        searchResult.setPageNum(searchParam.getPageNum());
        //分页信息-总页码-计算
        int totalPages = total % SearchConstant.PRODUCT_PAGESIZE == 0 ?
                total / SearchConstant.PRODUCT_PAGESIZE : (total / SearchConstant.PRODUCT_PAGESIZE + 1);
        searchResult.setTotalPages(totalPages);
    }
}
